﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace MulticastMessaging
{
    using System.Net.Sockets;
    using System.Text;

    public class MessagingStatusEventArgs : EventArgs
    {
        public string GroupName { get; set; }
        public string Status { get; set; }
    }

    public class MessagingRecievedEventArgs : EventArgs
    {
        public string GroupName { get; set; }
        public IPEndPoint Sender { get; set; }
        public string Message { get; set; }
    }

    public class MulticastGroup
    {
        private const string GROUP_ADDRESS = "224.0.1.1";
        private const int GROUP_PORT = 52274;
        private const int READ_BUFFER_BYTES = 512;

        public string GroupAddress { get; set; }
        public int? GroupPort { get; set; }
        public string GroupName { get; set; }
        public int? ReadBufferBytes { get; set; }
        public int JoinedMembers { get; set; }
        public bool Joined
        {
            get { return joined; }
        }

        private UdpAnySourceMulticastClient myClient;
        IPAddress groupAddress;
        IPAddress myAddress = null;
        private byte[] receiveBuffer;
        private bool joined = false;
        private Guid myJKPID;

        public MulticastGroup()
        {
            JoinedMembers = 0;
        }

        public bool Join()
        {
            myJKPID = Guid.NewGuid();
            
            if (String.IsNullOrWhiteSpace(GroupName))
            {
                throw new InvalidOperationException("Should set GroupName before this message calling.");
            }

            if (String.IsNullOrWhiteSpace(GroupAddress))
            {
                GroupAddress = GROUP_ADDRESS;
            }
            if (GroupPort==null){
                GroupPort=GROUP_PORT;
            }
            if (ReadBufferBytes==null){
                ReadBufferBytes = READ_BUFFER_BYTES;
            }
            receiveBuffer = new byte[ReadBufferBytes.Value];

            groupAddress = IPAddress.Parse(GroupAddress);
            myClient = new UdpAnySourceMulticastClient(groupAddress, GroupPort.Value);
            try
            {
                myClient.BeginJoinGroup(
                    result =>
                    {
                        try
                        {
                            myClient.EndJoinGroup(result);
                            myClient.MulticastLoopback = true;
                            joined = true;
                            Send(String.Format("Join:{0}", myJKPID));
                            Receive();
                            OnStatusChanged("Joined");
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show("Join succeeded. but something wrong. " + ex.Message);
                        }
                    }, null
                );
            }
            catch (Exception ex)
            {
                MessageBox.Show("Join failed. " + ex.Message);
            }

            return joined;
        }

        public void Send(string message)
        {

            byte[] msg = Encoding.UTF8.GetBytes(String.Format("{0}:{1}", GroupName, message));
            myClient.BeginSendToGroup(msg, 0, msg.Length,
                result =>
                {
                    myClient.EndSendToGroup(result);
                    OnStatusChanged("Send");
                }, null
            );
        }

        private void Receive()
        {
            if (joined)
            {
                Array.Clear(receiveBuffer, 0, receiveBuffer.Length);
                myClient.BeginReceiveFromGroup(receiveBuffer, 0, receiveBuffer.Length,
                    result =>
                    {
                        try
                        {
                            IPEndPoint source;
                            myClient.EndReceiveFromGroup(result, out source);
                            string dataReceived = Encoding.UTF8.GetString(receiveBuffer, 0, receiveBuffer.Length).TrimEnd(new char[] { '\0' });
                            string sourceIPAddress = source.Address.ToString();

                            if (dataReceived.StartsWith(GroupName + ":"))
                            {
                                dataReceived = dataReceived.Substring(GroupName.Length + 1);
                                if (myAddress == null)
                                {
                                    if (dataReceived.StartsWith("Join:"))
                                    {
                                        string receivedId = dataReceived.Substring("Join:".Length);
                                        if (receivedId.CompareTo(myJKPID.ToString()) == 0)
                                        {
                                            myAddress = source.Address;
                                        }
                                        else
                                        {
                                            JoinedMembers++;
                                            OnDataReceived(source,"Joined");
                                        }
                                    }
                                }
                                else
                                {
                                    string myIPAddress = myAddress.ToString();
                                    if (sourceIPAddress.CompareTo(myIPAddress) != 0)
                                    {
                                        dataReceived = String.Format("{0}", dataReceived);
                                        OnDataReceived(source, dataReceived);
                                        OnStatusChanged("Received");
                                    }
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show("Error happend in receiving. " + ex.Message);
                        }
                        Receive();
                    }, null);
            }
        }

        public event EventHandler<MessagingStatusEventArgs> StatusChanged;
        public event EventHandler<MessagingRecievedEventArgs> MessageReceived;

        private void OnStatusChanged(string status)
        {
            if (StatusChanged != null)
            {
                StatusChanged(this,
                    new MessagingStatusEventArgs()
                    {
                        GroupName = GroupName,
                        Status = status
                    });
            }
        }

        private void OnDataReceived(IPEndPoint sender, string data)
        {
            if (MessageReceived != null)
            {
                MessageReceived(this, new MessagingRecievedEventArgs()
                {
                    GroupName = GroupName,
                    Sender = sender,
                    Message = data
                });
            }
        }
    }
}
